package com.spn.blog.article.service;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spn.blog.article.component.rocketmq.ArticleToESProducer;
import com.spn.blog.article.component.rocketmq.R;
import com.spn.blog.article.dao.mapper.ArticleBodyMapper;
import com.spn.blog.article.dao.mapper.ArticleMapper;
import com.spn.blog.article.dao.mapper.ArticleTagMapper;
import com.spn.blog.article.dao.mapper.CommentsMapper;
import com.spn.blog.article.pojo.ArticleStatus;
import com.spn.blog.common.dao.dos.Archives;
import com.spn.blog.common.dao.pojo.*;
import com.spn.blog.common.utils.RedisCacheUtil;
import com.spn.blog.common.vo.*;
import com.spn.blog.common.vo.params.ArticleParam;
import com.spn.blog.common.vo.params.PageParams;
import com.spn.blog.service.article.ArticleService;
import com.spn.blog.service.article.CategoryService;
import com.spn.blog.service.article.TagService;
import com.spn.blog.service.login.SysUserService;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.MatchPhrasePrefixQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@DubboService
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private TagService tagService;
    @Autowired
    private ArticleTagMapper articleTagMapper;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private ArticleBodyMapper articleBodyMapper;
    @Autowired
    private CommentsMapper commentsMapper;
    @Autowired
    private ThreadService threadService;
    @Autowired
    private ESservice eSservice;
    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;
    @DubboReference
    private SysUserService userService;
    @Value("${es.index}")
    private String index;


    /**
     * 代替了以前的 查询Article方法，（以前功能：分页查询、条件CategoryId，TagId查询）
     * 新功能（条件 年，月查询）
     *
     * @param pageParams
     * @return
     */
    @Override
    public Result listArticle(PageParams pageParams) {
        Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
        //以前是Page<Article> articlePage接受，传入的参数是（page，queryWrapper）
        //现在是IPage<Article> articleIPage接受，传入的参数是(page，自己想要的参数...）
        IPage<Article> articleIPage = articleMapper.listArticle(
                page,
                pageParams.getCategoryId(),
                pageParams.getTagId(),
                pageParams.getYear(),
                pageParams.getMonth());
        List<Article> records = articleIPage.getRecords();
        List<ArticleVo> articleVos = copyList(records, true, true);
        return Result.success(articleVos);
    }

    private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) {
        List<ArticleVo> articleVoList = new ArrayList<>();
        for (Article record : records) {
            articleVoList.add(copy(record, isTag, isAuthor, false, false));
        }
        return articleVoList;
    }

    private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {
        ArticleVo articleVo = new ArticleVo();
        //因为目标对象和原始对象，id属性类型不一致，所以需要手动赋值
//        articleVo.setId(String.valueOf(article.getId()));
        BeanUtils.copyProperties(article, articleVo);
        //因为目标对象和原始对象，createDate属性类型不一致，所以需要手动赋值
        //一边是毫秒显示，一边是yyyy-MM-dd HH:mm类型显示
        articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
        if (isTag) {
            String articleId = article.getId();
            articleVo.setTags(tagService.findTagsByArticleId(articleId));
        }
        if (isAuthor) {
            String authorId = article.getAuthorId();
            SysUser sysUser = userService.findUserById(authorId);
            UserVo userVo = new UserVo();
            userVo.setAvatar(sysUser.getAvatar());
            userVo.setNickname(sysUser.getNickname());
            userVo.setId(String.valueOf(sysUser.getId()));
            articleVo.setAuthor(userVo);
        }
        if (isBody) {
            String bodyId = article.getBodyId();
            articleVo.setBody(findArticleBodyById(bodyId));
        }
        if (isCategory) {
            String categoryId = article.getCategoryId();
            articleVo.setCategory(categoryService.findCategoryById(categoryId));
        }


        return articleVo;
    }

    private ArticleBodyVo findArticleBodyById(String bodyId) {
        ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
        ArticleBodyVo articleBodyVo = new ArticleBodyVo();
        articleBodyVo.setContent(articleBody.getContent());
        return articleBodyVo;
    }

    @Override
    public Result hotArticle(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        //select id,title from article order by view_counts desc limit 5
        queryWrapper
                .orderByDesc(Article::getViewCounts)//设置用哪个字段排序
                .select(Article::getId, Article::getTitle)//设置最后结果需要的字段
                .last("limit " + limit);
        List<Article> articles = articleMapper.selectList(queryWrapper);
        return Result.success(copyList(articles, false, false));
    }

    @Override
    public Result newArticle(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        //select id,title from article order by create_date desc limit 5
        queryWrapper
                //设置用哪个字段排序
                .orderByDesc(Article::getCreateDate)
                //设置最后结果需要的字段
                .select(Article::getId, Article::getTitle)
                .last("limit " + limit);
        List<Article> articles = articleMapper.selectList(queryWrapper);
        return Result.success(copyList(articles, false, false));
    }

    @Override
    public Result listArchives() {
        List<Archives> archivesList = articleMapper.listArchives();
        return Result.success(archivesList);
    }

    @Override
    public Result findArticleById(Long articleId) {
        /**
         * 1. 根据id查询，文章信息
         * 2. 根据bodyId和categoryId 做关联查询
         */
        Article article = articleMapper.selectById(articleId);
        ArticleVo articleVo = copy(article, true, true, true, true);
        //查看完文章后，增加阅读数？此处增加有问题？
        //在这本应该返回数据了，这时候做了一个更新操作，更新时数据库加写锁，阻塞其他的读操作，性能就会比较低。
        //更新 增加此接口的耗时，如果一旦更新出问题，不能影响查看文章的操作。线程池解决这个问题：
        //线程池：可以把更新操作扔到线程池中取执行，和主线程就不相关了。

        threadService.updateArticleViewCount(articleMapper, article);
        return Result.success(articleVo);
    }

    @Autowired
    private ArticleToESProducer articleToESProducer;

    @Override
    @Transactional
    public Result publish(ArticleParam articleParam, SysUser sysUser) {
        /**
         * 1. 发布文章 目的：构建 Article对象
         * 2. 作者ID， 当前登录用户
         * 3. 标签    将标签加入到关联列表当中
         * 4. body   内容存储; article表 里面需要 bodyId
         */
        Article article = new Article();
        article.setDeleted(0);
        boolean isEdit = false;
        if (StringUtils.isNotBlank(articleParam.getId())) {
            //如果id不为空，说明前端发起的编辑请求
            article.setId(articleParam.getId());
            article.setTitle(articleParam.getTitle());
            article.setSummary(articleParam.getSummary());
            article.setCategoryId(articleParam.getCategory().getId());
            article.setStatus(String.valueOf(ArticleStatus.Processing.getCode()));
            articleMapper.updateById(article);
            isEdit = true;

        } else {
            article.setAuthorId(sysUser.getId());
            article.setWeight(Article.Article_Common);
            article.setViewCounts(0);
            article.setTitle(articleParam.getTitle());
            article.setSummary(articleParam.getSummary());
            article.setCommentCounts(0);
            article.setCreateDate(System.currentTimeMillis());
            article.setCategoryId(articleParam.getCategory().getId());
            article.setStatus(String.valueOf(ArticleStatus.Processing.getCode()));
            article.setTopicImg(articleParam.getTopicImg());
            // 插入文章，生成文章id
            //官网解释：insert 后主键会自动 set 到实体的 ID 字段，所以你只需要 getId() 就好
            articleMapper.insert(article);
        }

        // 把文章提交到rocketmq
//        kafkaTemplate.send(C.KAFKA_TOPIC_ES_Article_NAME, "key", article);
        articleToESProducer.syncSendOrderly(R.ROCKETMQ_TOPIC_ES_Article_NAME, article, article.getId());

        //tag
        List<TagVo> tags = articleParam.getTags();
        if (tags != null) {
            for (TagVo tag : tags) {
                String articleId = article.getId();
                if (isEdit) {
                    //先删除
                    LambdaQueryWrapper<ArticleTag> queryWrapper = Wrappers.lambdaQuery();
                    queryWrapper.eq(ArticleTag::getArticleId, articleId);
                    articleTagMapper.delete(queryWrapper);
                }
                ArticleTag articleTag = new ArticleTag();
                articleTag.setTagId(tag.getId());
                articleTag.setArticleId(article.getId());
                articleTagMapper.insert(articleTag);
            }
        }
        //body
        if (isEdit) {
            ArticleBody articleBody = new ArticleBody();
            articleBody.setArticleId(article.getId());
            articleBody.setContent(articleParam.getBody().getContent());
            articleBody.setContentHtml(articleParam.getBody().getContentHtml());
            LambdaUpdateWrapper<ArticleBody> updateWrapper = Wrappers.lambdaUpdate();
            updateWrapper.eq(ArticleBody::getArticleId, article.getId());
            articleBodyMapper.update(articleBody, updateWrapper);
        } else {
            ArticleBody articleBody = new ArticleBody();
            articleBody.setArticleId(article.getId());
            articleBody.setContent(articleParam.getBody().getContent());
            articleBody.setContentHtml(articleParam.getBody().getContentHtml());
            articleBodyMapper.insert(articleBody);
            article.setBodyId(articleBody.getId());
            articleMapper.updateById(article);
        }

        Map<String, String> map = new HashMap<>();
        map.put("id", article.getId());

        //此处发送一条消息给redis 当前文章更新了，更新一下缓存


        return Result.success(map);
    }

    @Override
    public Result searchArticle(String search) {
        //做一个搜索的模糊匹配
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getViewCounts);
        queryWrapper.select(Article::getId, Article::getTitle);
        queryWrapper.like(Article::getTitle, search);
        List<Article> articles = articleMapper.selectList(queryWrapper);
        return Result.success(copyList(articles, false, false));
    }

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Transactional
    @Override
    public Result deleteArticleById(Long articleId) {
        // 删除tag关联ag
        LambdaQueryWrapper<ArticleTag> queryWrapperTag = new LambdaQueryWrapper<>();
        queryWrapperTag.eq(ArticleTag::getArticleId, articleId);
        articleTagMapper.delete(queryWrapperTag);

        // 删除comment关联
        LambdaQueryWrapper<Comment> queryWrapperComment = new LambdaQueryWrapper<>();
        queryWrapperComment.eq(Comment::getArticleId, articleId);
        commentsMapper.delete(queryWrapperComment);

        // 删除文章body
        LambdaQueryWrapper<ArticleBody> queryWrapperBody = new LambdaQueryWrapper<>();
        queryWrapperBody.eq(ArticleBody::getArticleId, articleId);
        articleBodyMapper.delete(queryWrapperBody);

        // 删除文章
        LambdaQueryWrapper<Article> queryWrapperArticle = new LambdaQueryWrapper<>();
        queryWrapperArticle.eq(Article::getId, articleId);
        articleMapper.delete(queryWrapperArticle);

        // 重新刷新ES数据库
        moveArticleToES();

        return Result.success(null);
    }

    @Override
    public Result islike(Long articleId, Boolean choice, SysUser currentSysUser) {
        // 1. 获取登录用户 currentSysUser
        Article article = articleMapper.selectById(articleId);
        // 判断是初始化显示（false），还是手动点击 “赞”（true）
        // 2. 判断当前用户是否点赞
        String key = "blog:liked:" + articleId;
        if (choice) {
            //这里使用tostring，不然会报错 class java.lang.Long cannot be cast to class java.lang.String
            Boolean isMember = redisTemplate.opsForSet().isMember(key, currentSysUser.getId().toString());
            if (BooleanUtils.isFalse(isMember)) {
                // 3. 如果未点赞，可以点赞
                //  3.1 数据库点赞数 + 1
                LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Article::getId, articleId);
                article.setLikeCounts((article.getLikeCounts() + 1));
                int update = articleMapper.update(article, queryWrapper);
                //  3.2 保存用户到Redis的set集合
                if (update == 1) {
                    redisTemplate.opsForSet().add(key, currentSysUser.getId().toString());
                }
                return Result.success(true);
            } else {
                // 4. 如果点赞，取消点赞
                //  4.1 数据库点赞数 - 1
                LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Article::getId, articleId);
                article.setLikeCounts((article.getLikeCounts() - 1));
                int update = articleMapper.update(article, queryWrapper);
                //  4.2 把用户用户从Redis的set集合删除
                if (update == 1) {
                    redisTemplate.opsForSet().remove(key, currentSysUser.getId().toString());
                }
                return Result.success(false);
            }
        } else {
            // 查看redis是否有此用户的点赞记录
            Boolean isMember = redisTemplate.opsForSet().isMember(key, currentSysUser.getId().toString());
            if (BooleanUtils.isTrue(isMember)) {
                return Result.success(true);
            } else {
                return Result.success(false);
            }
        }

    }

    @Override
    public Result moveArticleToES() {
        Boolean res = false;
        List<Article> articles = articleMapper.selectList(null);
        try {
            res = eSservice.cpOnEs(articles);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (res) return Result.success(res);
        return Result.fail(500, "ES服务器创建索引失败");
    }

    @Override
    public Result searchHighLight(String search) {

        SearchRequest searchRequest = new SearchRequest(index);
        // 条件构造器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        MatchPhrasePrefixQueryBuilder title1 = QueryBuilders.matchPhrasePrefixQuery("title", search);
        searchSourceBuilder.query(title1);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.requireFieldMatch(false);
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        // 执行搜索
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 结果解析
        ArrayList<Map<String, Object>> lists = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField title = highlightFields.get("title");
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();// 原来的结果
            // 解析高亮的字段，将原来的字段替换为高亮字段
            if (title != null) {
                Text[] fragments = title.fragments();
                String n_title = "";
                for (Text text : fragments) {
                    n_title += text;
                }
                sourceAsMap.put("title", n_title);
            }
            lists.add(sourceAsMap);
        }

        return Result.success(lists);
    }

    @Override
    public Result getArticleReview(Integer page, Integer pageSize) {
        Page<Article> pages = new Page<>(page, pageSize);
        //以前是Page<Article> articlePage接受，传入的参数是（page，queryWrapper）
        //现在是IPage<Article> articleIPage接受，传入的参数是(page，自己想要的参数...）
        IPage<Article> articleIPage = articleMapper.getArticleReview(pages);
        List<Article> records = articleIPage.getRecords();
        return Result.success(copyList(records, true, true));
    }

    @Override
    public Result isReview(String articleId, Boolean flag) {
        // falg：true 通过 false 不通过
        Article article = new Article();
        article.setId(articleId);
        article.setStatus(flag ? "0" : "1");
        articleMapper.updateById(article);
        return Result.success("操作成功");
    }

    /**
     * 该方法适用于单机
     * @param status
     * @return
     */
    @Override
    public Result getMyArticle(String status) {
/*        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        SysUser sysUser = loginUser.getSysUser();
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .eq(Article::getDeleted, "0")
                .eq(Article::getAuthorId, sysUser.getId())
                .orderByDesc(true,Article::getCreateDate);
        if (StringUtils.isNotBlank(status)){
            queryWrapper.eq(Article::getStatus, status);
        }
        List<Article> articles = articleMapper.selectList(queryWrapper);
        List<ArticleVo> articleVoList = copyList(articles, true, true);
        for (int i = 0; i < articleVoList.size(); i++) {
            ArticleVo articleVo = articleVoList.get(i);
            switch (articleVoList.get(i).getStatus()) {
                case "0":
                    articleVo.setStatus("审核通过");
                    break;
                case "1":
                    articleVo.setStatus("审核不通过");
                    break;
                case "2":
                    articleVo.setStatus("正在处理");
                    break;
                default: break;
            }
            articleVoList.set(i, articleVo);
        }*/
        return Result.success(null);
    }

    @Override
    public Result getMyArticle(String status, LoginUser loginUser) {
        SysUser sysUser = loginUser.getSysUser();
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .eq(Article::getDeleted, "0")
                .eq(Article::getAuthorId, sysUser.getId())
                .orderByDesc(true,Article::getCreateDate);
        if (StringUtils.isNotBlank(status)){
            queryWrapper.eq(Article::getStatus, status);
        }
        List<Article> articles = articleMapper.selectList(queryWrapper);
        List<ArticleVo> articleVoList = copyList(articles, true, true);
        for (int i = 0; i < articleVoList.size(); i++) {
            ArticleVo articleVo = articleVoList.get(i);
            switch (articleVoList.get(i).getStatus()) {
                case "0":
                    articleVo.setStatus("审核通过");
                    break;
                case "1":
                    articleVo.setStatus("审核不通过");
                    break;
                case "2":
                    articleVo.setStatus("正在处理");
                    break;
                default: break;
            }
            articleVoList.set(i, articleVo);
        }
        return Result.success(articleVoList);
    }

}
